package springboot.study.project.util.es;

import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.admin.indices.exists.types.TypesExistsRequest;
import org.elasticsearch.action.admin.indices.exists.types.TypesExistsResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.avg.InternalAvg;
import org.elasticsearch.search.aggregations.metrics.cardinality.InternalCardinality;
import org.elasticsearch.search.aggregations.metrics.max.InternalMax;
import org.elasticsearch.search.aggregations.metrics.min.InternalMin;
import org.elasticsearch.search.aggregations.metrics.sum.Sum;
import org.elasticsearch.search.aggregations.metrics.valuecount.InternalValueCount;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import springboot.study.project.common.PagingInfo;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutionException;

/**
 * @ClassName: ESUtil
 * @Description: ES工具类
 * @Author: baoxia
 * @Date: 2020-07-08 23:27
 * @Version: 1.0
 **/
@Component
public class ESUtil {

    /**
     * 初始化ES客户端
     */
    private static TransportClient client = ESClient.getInstance();

    /**
     * Gson的JSON转化对象
     */
    private static Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();

    /**
     * 日志打印对象
     */
    private static Logger logger = LoggerFactory.getLogger(ESUtil.class);


    /**
     * 创建索引库
     *
     * @param indexName 索引库名称
     */
    public static void createIndex(String indexName) {
        client.admin().indices().prepareCreate(indexName).get();
    }

    /**
     * 删除索引库
     *
     * @param indexName 索引库名称
     */
    public static void deleteIndex(String indexName) {
        client.admin().indices().prepareDelete(indexName).get();
    }

    /**
     * 向索引库添加 Mapping映射
     *
     * @param indexName 索引库名称
     * @param indexType 索引类型
     * @param mapping   XContentBuilder类型的 Mapping信息
     * @throws IOException IO异常
     */
    public static void addMapping(String indexName, String indexType, XContentBuilder mapping) throws IOException {
        // 使用client把mapping信息设置到索引库中
        client.admin().indices()
                // 设置要添加映射的索引库名称
                .preparePutMapping(indexName)
                // 设置要做映射的索引库中的type（与数据库中的表对应）
                .setType(indexType)
                // 添加创建好的mapping信息，可以是XContentBuilder信息，也可以是json格式的字符串
                .setSource(mapping)
                // 执行操作
                .get();
    }

    /**
     * 添加文档操作
     *
     * @param indexName  索引库名称
     * @param indexType  索引类型
     * @param documentId 文档ID
     * @param document   文档对象（对应的 Pojo）
     */
    public static void addDocument(String indexName, String indexType, String documentId, Object document) {
        client.prepareIndex()
                // 设置文档索引
                .setIndex(indexName)
                // 设置文档Type
                .setType(indexType)
                // 设置文档ID
                .setId(documentId)
                // 设置文档信息
                .setSource(JSON.toJSONString(document), XContentType.JSON)
                // 执行方法
                .get();
    }

    /**
     * 根据文档 ID删除文档
     *
     * @param indexName  索引库名称
     * @param indexType  索引类型
     * @param documentId 文档ID
     */
    public static void deleteDocument(String indexName, String indexType, String documentId) {
        client.prepareDelete()
                // 设置文档索引
                .setIndex(indexName)
                // 设置文档Type
                .setType(indexType)
                // 设置文档ID
                .setId(documentId)
                // 执行方法
                .get();
    }

    /**
     * 判断指定的索引名是否存在
     *
     * @param indexName 索引名
     * @return 存在：true; 不存在：false;
     */
    public static boolean isExistsIndex(String indexName) {
        IndicesExistsResponse response = client.admin().indices()
                .exists(new IndicesExistsRequest().indices(new String[]{indexName}))
                .actionGet();
        return response.isExists();
    }

    /**
     * 判断指定的索引的类型是否存在
     *
     * @param indexName 索引名
     * @param indexType 索引类型
     * @return 存在：true; 不存在：false;
     */
    public static boolean isExistsType(String indexName, String indexType) {
        TypesExistsResponse response = client.admin().indices()
                .typesExists(new TypesExistsRequest(new String[]{indexName}, indexType))
                .actionGet();
        return response.isExists();
    }

    /**
     * 根据文档ID进行查询
     *
     * @param indexName  索引名
     * @param indexType  索引类型
     * @param documentId 文档ID
     * @return 命中的记录
     */
    public static Map<String, Object> getDocumentById(String indexName, String indexType, String documentId) {

        // 执行数据查询
        GetResponse getResponse = client.prepareGet(indexName, indexType, documentId).get();
        // 获取查询的数据
        Map<String, Object> source = getResponse.getSourceAsMap();
        return source;
    }

    /**
     * 根据查询条件进行ES检索
     *
     * @param indexName    索引名
     * @param indexType    索引类型
     * @param queryBuilder 查询条件
     * @return 查询到的文档数量
     */
    public static long querySearchCount(String indexName, String indexType, QueryBuilder queryBuilder) {
        // 执行查询
        SearchResponse searchResponse = client.prepareSearch()
                .setIndices(indexName)
                .setTypes(indexType)
                .setQuery(queryBuilder)
                .get();

        // 取查询结果
        SearchHits searchHits = searchResponse.getHits();
        return searchHits.getTotalHits();
    }

    /**
     * 根据查询条件进行ES检索
     *
     * @param indexName    索引名
     * @param indexType    索引类型
     * @param queryBuilder 查询条件对象
     * @return 查询到的数据集合
     */
    public static List<Map<String, Object>> querySearch(String indexName, String indexType, QueryBuilder queryBuilder) {
        // 执行查询
        SearchResponse searchResponse = client.prepareSearch()
                .setIndices(indexName)
                .setTypes(indexType)
                .setQuery(queryBuilder)
                // 设置起始行号
                .setFrom(0)
                // 每页大小
                .setSize(10000)
                .get();

        // 取查询结果
        SearchHits searchHits = searchResponse.getHits();

        // 查询结果列表
        Iterator<SearchHit> iterator = searchHits.iterator();
        List<Map<String, Object>> list = new ArrayList<>();
        while (iterator.hasNext()) {
            // 获取查询对象
            SearchHit searchHit = iterator.next();
            // 转化成Map
            Map<String, Object> source = searchHit.getSource();
            // 取文档的属性
            list.add(source);
        }
        return list;
    }

    /**
     * 根据查询条件进行ES检索
     *
     * @param indexName    索引名
     * @param indexType    索引类型
     * @param queryBuilder 查询条件对象
     * @param page         起始页码
     * @param size         每页显示个数
     * @return 查询到的数据集合
     */
    public static List<Map<String, Object>> querySearch(String indexName, String indexType, QueryBuilder queryBuilder, int page, int size) {
        int from = (page - 1) * size;
        // 执行查询
        SearchResponse searchResponse = client.prepareSearch()
                .setIndices(indexName)
                .setTypes(indexType)
                .setQuery(queryBuilder)
                // 设置起始行号
                .setFrom(from)
                // 每页大小
                .setSize(size)
                .get();

        // 取查询结果
        SearchHits searchHits = searchResponse.getHits();

        // 查询结果列表
        Iterator<SearchHit> iterator = searchHits.iterator();
        List<Map<String, Object>> list = new ArrayList<>();
        while (iterator.hasNext()) {
            // 获取查询对象
            SearchHit searchHit = iterator.next();
            // 转化成Map
            Map<String, Object> source = searchHit.getSource();
            // 取文档的属性
            list.add(source);
        }
        return list;
    }

    /**
     * 根据查询条件进行ES检索
     *
     * @param indexName    索引名
     * @param indexType    索引类型
     * @param queryBuilder 查询条件对象
     * @param page         起始页码
     * @param size         每页显示个数
     * @param sortBuilder  指定字段排序
     * @return 查询到的数据集合
     */
    public static List<Map<String, Object>> querySearch(String indexName, String indexType, QueryBuilder queryBuilder, int page, int size, SortBuilder sortBuilder) {
        int from = (page - 1) * size;
        // 执行查询
        SearchResponse searchResponse = client.prepareSearch()
                .setIndices(indexName)
                .setTypes(indexType)
                .setQuery(queryBuilder)
                // 设置起始行号
                .setFrom(from)
                // 每页大小
                .setSize(size)
                // 指定排序规则
                .addSort(sortBuilder)
                .get();

        // 获取查询结果
        SearchHits searchHits = searchResponse.getHits();

        // 查询结果列表
        Iterator<SearchHit> iterator = searchHits.iterator();
        List<Map<String, Object>> list = new ArrayList<>();
        while (iterator.hasNext()) {
            // 获取查询对象
            SearchHit searchHit = iterator.next();
            // 转化成Map
            Map<String, Object> source = searchHit.getSource();
            // 取文档的属性
            list.add(source);
        }
        return list;
    }

    /**
     * 根据查询条件进行ES检索（直接返回目标对象集合）
     * 参考链接：https://blog.csdn.net/hu582205/article/details/84873952
     *
     * @param indexName    索引名
     * @param indexType    索引类型
     * @param queryBuilder 查询条件对象
     * @param clazz        返回结果的实体类的类型
     * @return 查询到的数据集合
     */
    public static <E> List<E> querySearch(String indexName, String indexType, QueryBuilder queryBuilder, Class<E> clazz) {
        PagingInfo pagingInfo = new PagingInfo(1, 10000);
        // 执行查询
        SearchResponse searchResponse = client.prepareSearch()
                .setIndices(indexName)
                .setTypes(indexType)
                .setQuery(queryBuilder)
                // 设置起始行号
                .setFrom(pagingInfo.getOffset())
                // 每页大小
                .setSize(pagingInfo.getLimit())
                .get();

        // 获取查询结果
        logger.debug("es 查询结果：{}", searchResponse.toString());
        SearchHit[] hits = searchResponse.getHits().getHits();

        List<E> list = new ArrayList<>();
        for (SearchHit hit : hits) {
            E e = JSON.parseObject(gson.toJson(hit.getSource()), clazz);
            list.add(e);
        }
        return list;
    }

    /**
     * 根据查询条件进行ES检索（直接返回目标对象集合）
     * 参考链接：https://blog.csdn.net/hu582205/article/details/84873952
     *
     * @param indexName    索引名
     * @param indexType    索引类型
     * @param queryBuilder 查询条件对象
     * @param pagingInfo   分页信息
     * @param clazz        返回结果的实体类的类型
     * @return 查询到的数据集合
     */
    public static <E> List<E> querySearch(String indexName, String indexType, QueryBuilder queryBuilder, PagingInfo pagingInfo, Class<E> clazz) {
        // 执行查询
        SearchResponse searchResponse = client.prepareSearch()
                .setIndices(indexName)
                .setTypes(indexType)
                .setQuery(queryBuilder)
                // 设置起始行号
                .setFrom(pagingInfo.getOffset())
                // 每页大小
                .setSize(pagingInfo.getLimit())
                .get();

        // 获取查询结果
        logger.debug("es 查询结果：{}", searchResponse.toString());
        SearchHit[] hits = searchResponse.getHits().getHits();

        List<E> list = new ArrayList<>();
        for (SearchHit hit : hits) {
            E e = JSON.parseObject(gson.toJson(hit.getSource()), clazz);
            list.add(e);
        }
        return list;
    }

    /**
     * 根据查询条件进行ES检索（直接返回目标对象集合）
     * 参考链接：https://blog.csdn.net/hu582205/article/details/84873952
     *
     * @param indexName    索引名
     * @param indexType    索引类型
     * @param queryBuilder 查询条件对象
     * @param pagingInfo   分页信息
     * @param clazz        返回结果的实体类的类型
     * @param sortBuilder  指定字段排序
     * @return 查询到的数据集合
     */
    public static <E> List<E> querySearch(String indexName, String indexType, QueryBuilder queryBuilder, PagingInfo pagingInfo, SortBuilder sortBuilder, Class<E> clazz) {
        // 执行查询
        SearchResponse searchResponse = client.prepareSearch()
                .setIndices(indexName)
                .setTypes(indexType)
                .setQuery(queryBuilder)
                // 设置起始行号
                .setFrom(pagingInfo.getOffset())
                // 每页大小
                .setSize(pagingInfo.getLimit())
                // 指定排序规则
                .addSort(sortBuilder)
                .get();

        // 获取查询结果
        logger.debug("es 查询结果：{}", searchResponse.toString());
        SearchHit[] hits = searchResponse.getHits().getHits();

        List<E> list = new ArrayList<>();
        for (SearchHit hit : hits) {
            E e = JSON.parseObject(gson.toJson(hit.getSource()), clazz);
            list.add(e);
        }
        return list;
    }


    /**
     * 分组聚合查询
     *
     * @param indexName     索引名
     * @param indexType     索引类型
     * @param sourceBuilder 查询聚合条件
     * @return
     */
    public static List<Map<String, Object>> querySearch(String indexName, String indexType, SearchSourceBuilder sourceBuilder, String groupName) {
        SearchResponse searchResponse = null;
        try {
            //查询索引对象
            SearchRequest searchRequest = new SearchRequest(indexName);
            searchRequest.types(indexType);
            searchRequest.source(sourceBuilder);
            searchResponse = client.search(searchRequest).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        return results(searchResponse, groupName);
    }

    private static List<Map<String, Object>> results(SearchResponse searchResponse, String groupName) {
        List<Map<String, Object>> list = new ArrayList<>();
        Terms termsCurrent = searchResponse.getAggregations().get(groupName);
        for (Terms.Bucket bucket : termsCurrent.getBuckets()) {
            Map<String, Object> map = package2map(bucket);
            map.put("key", bucket.getKeyAsString());
            list.add(map);
            System.out.println(map);
        }
        return list;
    }

    private static LinkedHashMap<String, Object> package2map(Terms.Bucket bucket) {
        LinkedHashMap<String, Object> map = new LinkedHashMap<>();

        for (Map.Entry<String, Aggregation> entry : bucket.getAggregations().asMap().entrySet()) {
            String key = entry.getKey();
            Aggregation value = entry.getValue();
            map.put(key, getvalue(value));
        }
        return map;
    }

    //取值
    public static String getvalue(Aggregation agg) {
        String type = agg.getType();

        String result = "";
        switch (type) {
            case "avg":
                result = String.valueOf(((InternalAvg) agg).getValue());
                break;
            case "sum":
                result = String.valueOf(((Sum) agg).getValue());
                break;
            case "value_count":
                result = String.valueOf(((InternalValueCount) agg).getValue());
                break;
            case "min":
                result = String.valueOf(((InternalMin) agg).getValue());
                break;
            case "max":
                result = String.valueOf(((InternalMax) agg).getValue());
                break;
            case "cardinality":
                result = String.valueOf(((InternalCardinality) agg).getValue());
                break;
            default:
                result = String.valueOf(agg);
                break;

        }
        return result;
    }

    /**
     * 关闭客户端链接
     */
    public static void closeClint() {
        client.close();
    }

}
